﻿#region Copyright © 2013 Paul Miller (Millz)
// All rights are reserved. Reproduction or transmission in whole or in part,
// any form or by any means, electronic, mechanical or otherwise, is prohibited
// without the prior written consent of the copyright owner.
// 
// Filename: Shadow.cs
// Date:     23/09/2013
// Author:   Paul Miller (Millz)
#endregion

using System;
using System.Linq;
using CommonBehaviors.Actions;
using Insanity.Core;
using Insanity.Helpers;
using Insanity.Managers;
using Insanity.Settings;
using JetBrains.Annotations;
using Styx;
using Styx.Common;
using Styx.CommonBot;
using Styx.TreeSharp;
using Styx.WoWInternals.WoWObjects;
using Action = Styx.TreeSharp.Action;

namespace Insanity.Specialisation
{
    [UsedImplicitly]
    internal class Shadow : RotationBase
    {
        private static InsanitySettings Settings { get { return InsanitySettings.Instance; } }

        #region RotationBase Overrides
        public override WoWSpec KeySpec { get { return WoWSpec.PriestShadow;} }
        public override string Name { get { return "Shadow"; } }
        public override string Revision { get { return "$Rev: 335 $"; } }
        #endregion
        
        #region Rotation Handles
        public override Composite Rotation
        {
            get
            {
                return new PrioritySelector(
                    new Decorator(ret => Me.IsChanneling && Me.ChanneledCastingSpellId == SpellId.HymnOfHope, new ActionAlwaysSucceed()),

                    CachedUnits.Pulse,
                    SetVars,

                    new Decorator(ret => Spell.GlobalCooldown(), new ActionAlwaysSucceed()),

                    Common.SetCounts, // Set counts for our AoE trigger
                    Common.Combat(), // Handles all of our utility / talent abilities.

                    SpecAbilities(),

                    Spell.CastSelf("Shadowform", ret => !Me.HasAura("Shadowform")),
                    Spell.CastSelf("Inner Fire", ret => Settings.InnerFire && !Me.HasAura("Inner Fire")),
                    Spell.CastSelf("Inner Will", ret => Settings.InnerWill && !Me.HasAura("Inner Will")),

                    Spell.Cast("Shadowfiend", ret => !TalentManager.HasTalent(8) && Me.CurrentTarget.IsBossOrPlayer() && Me.ManaPercent < 95),
                    Spell.Cast("Mindbender", ret => Settings.UseMindbender && TalentManager.HasTalent(8) && Me.ManaPercent < 95),
                    PowerInfusion(),
                            
                    // AoE selector
                    new Decorator(ret => NeedAoE, HandleHighUnitAoECombat()),
                    new Decorator(ret => !NeedAoE, SingleTarget())
                    );
            }
        }
        
        private static Composite SingleTarget()
        {
            return new PrioritySelector(
                        // Force mind flay when Insanity.
                        Spell.PreventDoubleChannelClip("Mind Flay", 0.5, false, on => Me.CurrentTarget, ret => Me.CurrentTarget != null && TalentManager.HasTalent(9) && Me.CurrentTarget.HasAura("Devouring Plague")),
                        // Force cast to get a 3rd insanity in.
                        Spell.PreventDoubleChannelForceClip("Mind Flay", 0.5, false, on => Me.CurrentTarget, ret => Me.CurrentTarget != null && TalentManager.HasTalent(9) && Me.CurrentTarget.HasAura("Devouring Plague") && Spell.GetAuraTimeLeft("Devouring Plague", Me.CurrentTarget) < 0.3),
                        // don't go any further into rotation when insanity.
                        new Decorator(a => Me.CurrentTarget != null && ((TalentManager.HasTalent(9) && Me.CurrentTarget.HasAura("Devouring Plague")) || Me.ChanneledCastingSpellId == 129197), new ActionAlwaysSucceed()),
                        // stop MF cast
                        new Decorator(a => !CanMindFlay && Me.ChanneledCastingSpellId == 15407, new Action(delegate
                        {
                            Log.Info("Cancelling Mind Flay");
                            SpellManager.StopCasting(); return RunStatus.Failure;
                        })),
                        new Decorator(a => Me.ChanneledCastingSpellId == 48045, new Action(delegate
                        {
                            Log.Info("Cancelling Mind Sear - High AoE no longer required");
                            SpellManager.StopCasting(); return RunStatus.Failure;
                        })),

                        Spell.CastIgnoreMoving("Mind Blast", on => Me.CurrentTarget, ret => Me.HasAura(124430/*Divine Insight*/) && Me.IsFacing(Me.CurrentTarget)),

                        // SW:P must always be higher prio than dev plague to prevent the dot dropping
                        Spell.Cast("Devouring Plague", ret => _needDevouringPlague),
                        Spell.Cast("Mind Blast", ret => _currentShadowOrbs < 3),
                        Spell.Cast("Shadow Word: Death", ret => _currentShadowOrbs < 3 && Me.CurrentTarget.HealthPercent < 20),

                        Spell.PreventDoubleCast("Shadow Word: Pain", 1, ret => _needShadowWordPain),
                        Spell.PreventDoubleCast("Vampiric Touch", 2.2, ret => _needVampiricTouch),

                        Spell.Cast("Mind Spike", ret => Me.HasAura("Surge of Darkness")),

                        // AoE
                        new Decorator(ret => Settings.Shadow_EnableAoEAbilities, new PrioritySelector(                        
                            Spell.Cast("Shadow Word: Death", on => _shadowWordDeathUnit, ret => _shadowWordDeathUnit != null),
                            Spell.PreventDoubleMultiDoT("Shadow Word: Pain", 1, Me, 40, 3, ret => CanShadowWordPain),
                            Spell.PreventDoubleMultiDoT("Vampiric Touch", 2.2, Me, 40, 3, ret => CanVampiricTouch)
                        )),

                        Tier6Talents(),

                        Spell.PreventDoubleChannelClip("Mind Flay", 0.5, false, on => Me.CurrentTarget, ret => CanMindFlay),

                        // Moving
                        Spell.CastSelfOnGround("Angelic Feather", ret => Settings.AngelicFeatherInCombat && TalentManager.HasTalent(5) && !Me.HasAnyAura("Angelic Feather", "Glyph of Mind Flay") && (Me.IsMoving() || Me.HasAura("Spectral Guise"))),
                        Spell.CastSelf("Power Word: Shield", ret => Settings.BodyAndSoulInCombat && TalentManager.HasTalent(4) && !Me.HasAura("Weakened Soul") && Me.IsMoving())

                );
        }

        private static Composite HandleHighUnitAoECombat()
        {
            return new PrioritySelector(

                new Decorator(a => Me.ChanneledCastingSpellId == SpellId.MindFlay, new Action(delegate
                {
                    Log.Info("Cancelling Mind Flay - High AoE");
                    SpellManager.StopCasting(); return RunStatus.Failure;
                })),

                new Decorator(a => !CanMindSear && Me.ChanneledCastingSpellId == 48045, new Action(delegate
                {
                    Log.Info("Cancelling Mind Sear");
                    SpellManager.StopCasting(); return RunStatus.Failure;
                })),

                // Attempt to multi dot on new units without VT if our mana is low.
                Spell.MultiDoT("Vampiric Touch", Me, 40, 3, ret => Me.ManaPercent < 20),

                new Decorator(ret => Common._nearbyAoEUnitCount <= 5, new PrioritySelector(
                        Spell.MultiDoT("Shadow Word: Pain", Me, 40, 3, ret => CanShadowWordPain),
                        Spell.MultiDoT("Vampiric Touch", Me, 40, 3, ret => CanVampiricTouch)
                    )),

                Tier6Talents(),

                Spell.PreventDoubleChannelClip("Mind Sear", 0.5, false, on => Me.CurrentTarget, ret => CanMindSear),

                // Moving
                Spell.CastIgnoreMoving("Mind Blast", on => Me.CurrentTarget, ret => Me.HasAura(124430/*Divine Insight*/)),
                Spell.CastSelfOnGround("Angelic Feather", ret => Settings.AngelicFeatherInCombat && TalentManager.HasTalent(5) && !Me.HasAnyAura("Angelic Feather", "Glyph of Mind Flay") && Me.IsMoving()),
                Spell.CastSelf("Power Word: Shield", ret => Settings.BodyAndSoulInCombat && TalentManager.HasTalent(4) && !Me.HasAura("Weakened Soul") && Me.IsMoving())
                
                );
        }

        public override Composite PreCombat
        {
            get
            {
                return new Decorator(ret => !Me.IsGhost && !Me.IsDead && !Me.Mounted && !Me.IsFlying && !Me.IsOnTransport && !Me.HasAnyAura("Food", "Drink", "Waiting to Resurrect"),
                        new PrioritySelector(
                            new Decorator(ret => Spell.GlobalCooldown() || Spell.IsChanneling || Spell.IsCasting, new ActionAlwaysSucceed()),
                            Spell.CastSelf("Shadowform", ret => !Me.HasAura("Shadowform")),
                            Spell.CastSelf("Inner Fire", ret => Settings.InnerFire && !Me.HasAura("Inner Fire")),
                            Spell.CastSelf("Inner Will", ret => Settings.InnerWill && !Me.HasAura("Inner Will")),
                            Spell.CastRaidBuff("Power Word: Fortitude", ret => Settings.UsePowerWordFortitude),
                            Spell.CastSelfOnGround("Angelic Feather", ret => Settings.AngelicFeatherNoCombat && TalentManager.HasTalent(5) && !Me.HasAura("Angelic Feather") && Me.IsMoving()),
                            Spell.CastSelf("Power Word: Shield", ret => Settings.BodyAndSoulNoCombat && TalentManager.HasTalent(4) && Me.IsMoving() && !Me.HasAura("Weakened Soul")),
                            Common.FearWard()
                            ));
            }
        }

        #endregion

        #region Spec Composites

        private static Composite SpecAbilities()
        {
            return new PrioritySelector(
                VampiricEmbrace(),
                Dispersion()
                );
        }

        
        private static Composite VampiricEmbrace()
        {
            return new Decorator(ret => Settings.Shadow_UseVampiricEmbrace, new PrioritySelector(
                Spell.Cast("Vampiric Embrace", ret => Me.HealthPercent <= Settings.Shadow_VampiricEmbraceHealthPercent && !Settings.Shadow_VampiricEmbraceWaitForDP),
                Spell.Cast("Vampiric Embrace", ret => Me.HealthPercent <= Settings.Shadow_VampiricEmbraceHealthPercent && Settings.Shadow_VampiricEmbraceWaitForDP && _needDevouringPlague)
                ));
        }


        private static Composite Dispersion()
        {
            return new PrioritySelector(
                Spell.Cast("Dispersion", ret => Settings.Shadow_Dispersion_LowHP && Me.HealthPercent <= Settings.Shadow_Dispersion_LowHPValue),
                Spell.Cast("Dispersion", ret => Settings.Shadow_Dispersion_LowMana && Me.ManaPercent <= Settings.Shadow_Dispersion_LowManaValue)
                );
        }

        #endregion

        #region Talents

        /*
            On Cooldown
            On Boss Or Player
            On Target Low HP %
         */
        private static Composite PowerInfusion()
        {
            return new Decorator(ret => TalentManager.HasTalent(14), new PrioritySelector(
                Spell.Cast("Power Infusion", ret => Settings.PowerInfusionCondition == 0),
                Spell.Cast("Power Infusion", ret => Settings.PowerInfusionCondition == 1 && Me.CurrentTarget.IsBossOrPlayer()),
                Spell.Cast("Power Infusion", ret => Settings.PowerInfusionCondition == 2 && Me.CurrentTarget.HealthPercent < Settings.PowerInfusionLowHPValue))
                );
        }

        private static Composite Tier6Talents()
        {
            return new PrioritySelector(
                Spell.Cast("Cascade", ret => CanCascade),
                Spell.FaceAndCast("Divine Star", on => Me.CurrentTarget, ret => CanDivineStar),
                Spell.Cast("Halo", ret => CanHalo)
                );
        }
        #endregion

        #region SetVars

        private static bool _needShadowWordPain, _needVampiricTouch, _needDevouringPlague;
        private static uint _currentShadowOrbs;
        private static WoWUnit _shadowWordDeathUnit;
        
        private static Composite SetVars
        {
            get
            {
                return new Action(delegate
                {
                    try
                    {
                        if (Me.CurrentTarget == null) return RunStatus.Success;

                        _needShadowWordPain = NeedShadowWordPain;
                        _needVampiricTouch = NeedVampiricTouch;
                        _needDevouringPlague = NeedDevouringPlague;

                        _currentShadowOrbs = CurrentShadowOrbs;

                        // if AoE..
                        _shadowWordDeathUnit = BestShadowWordDeathUnit();

                    }
                    catch (Exception ex)
                    {
                        Log.Debug("SetVars failed with: {0}", ex);
                    }
                    return RunStatus.Failure;
                });
            }
        }

        #endregion

        #region Conditions

        private static uint CurrentShadowOrbs { get { return Me.GetCurrentPower(WoWPowerType.ShadowOrbs); } }

        private static bool NeedShadowWordPain
        {
            get
            {
                return !Me.CurrentTarget.HasAura("Shadow Word: Pain") || Spell.GetAuraTimeLeftMs("Shadow Word: Pain", Me.CurrentTarget) < Settings.Shadow_SWPForceRefreshTime;
            }
        }

        private static bool NeedVampiricTouch
        {
            get
            {
                if (Me.Level < 28)
                    return false;

                return !Me.CurrentTarget.HasAura("Vampiric Touch") || Spell.GetAuraTimeLeftMs("Vampiric Touch", Me.CurrentTarget) < Settings.Shadow_VTForceRefreshTime;
            }
        }

        private static bool NeedDevouringPlague
        {
            get
            {
                return Me.CurrentTarget != null &&
                       Me.Level >= 21 &&
                       CurrentShadowOrbs == 3 &&
                       Me.CurrentTarget.HasAllMyAuras("Shadow Word: Pain", "Vampiric Touch");
            }
        }

        private static bool CanMindFlay
        {
            get
            {

                if (Me.IsMoving())
                    return false;

                if (Me.Level >= 46 && Me.CurrentTarget != null && Me.CurrentTarget.HealthPercent <= 20 && !Spell.SpellOnCooldown("Shadow Word: Death"))
                    return false;

                if (Me.Level >= 21 && !Spell.SpellOnCooldown(SpellId.MindBlast))
                    return false;

                if (Me.Level >= 21 && SpellManager.CanCast("Mind Blast"))
                    return false;

                if (Me.ActiveAuras.ContainsKey("Surge of Darkness"))
                    return false;

                if (_needDevouringPlague)
                    return false;
                   
                if (_needShadowWordPain)
                    return false;

                if (_needVampiricTouch)
                    return false;

                if (Settings.Shadow_EnableAoEAbilities)
                {
                    if (CanShadowWordPain)
                    {
                        var shadowWordPainHighStatsUnit = Unit.GetMultiDoTTarget(Me, "Shadow Word: Pain", 40, 3);

                        if (shadowWordPainHighStatsUnit != null)
                            return false;
                    }

                    if (CanVampiricTouch)
                    {
                        var vampiricTouchHighStatsUnit = Unit.GetMultiDoTTarget(Me, "Vampiric Touch", 40, 3);

                        if (vampiricTouchHighStatsUnit != null)
                            return false;
                    }
                }

                if (CanCascade)
                    return false;

                if (CanDivineStar)
                    return false;

                if (CanHalo)
                    return false;

                return true;
            }
        }

        private static bool CanMindSear
        {
            get
            {
                if (Me.IsMoving())
                    return false;

                if (Me.ManaPercent < 5)
                    return false;

                if (Common._nearbyAoEUnitCount <= 5)
                {
                    if (CanShadowWordPain)
                    {
                        var shadowWordPainHighStatsUnit = Unit.GetMultiDoTTarget(Me, "Shadow Word: Pain", 40, 3);
                        
                        if (shadowWordPainHighStatsUnit != null)
                            return false;
                    }

                    if (CanVampiricTouch)
                    {
                        var vampiricTouchHighStatsUnit = Unit.GetMultiDoTTarget(Me, "Vampiric Touch", 40, 3);

                        if (vampiricTouchHighStatsUnit != null)
                            return false;
                    }
                }

                if (CanCascade)
                    return false;

                if (CanDivineStar)
                    return false;

                if (CanHalo)
                    return false;

                return true;


            }
        }

        private static bool CanShadowWordPain
        {
            get
            {
                if (Me.ManaPercent < 10)
                    return false;

                var val = Unit.CachedNearbyAttackableUnitsAttackingUs(Me.Location, 60)
                    .Count(u => u != null && Spell.GetMyAuraTimeLeft("Shadow Word: Pain", u) > 3) < Settings.Shadow_SWPMaxDoTsToCast;

                //Log.Debug("Can SWP: {0}", val);

                return val;
            }
        }

        private static bool CanVampiricTouch
        {
            get
            {
                if (Me.Level < 28)
                    return false;

                if (Me.ManaPercent < 10)
                    return true;

                return Unit.CachedNearbyAttackableUnitsAttackingUs(Me.Location, 60)
                    .Count(u => u != null && Spell.GetMyAuraTimeLeft("Vampiric Touch", u) > 3) < Settings.Shadow_VTMaxDoTsToCast;
            }
        }

        private static bool NeedAoE
        {
            get
            {
                return Settings.Shadow_EnableAoEAbilities &&
                Common._nearbyAoEUnitCount >= Settings.Shadow_AoEHighUnitCount && Me.Level >= 76;
            }
        }

        private static bool CanHalo
        {
            get
            {
                if (!Settings.UseHalo || !TalentManager.HasTalent(18))
                    return false;

                if (Spell.SpellOnCooldown("Halo"))
                    return false;

                var unit = Unit.CachedNearbyAttackableUnits(Me.Location, 33)
                    .FirstOrDefault(u => u != null && !u.IsTargetDummy() && !u.Combat && u.InLineOfSpellSight);

                if (unit != null)
                    Log.Orange("Unable to Halo -> {0} at {1}yds isn't in combat (which we would pull).", unit.SafeName, unit.Distance.ToString("F1"));

                return unit == null;

            }
        }

        private static bool CanCascade
        {
            get
            {
                return Settings.UseCascade && TalentManager.HasTalent(16) && !Spell.SpellOnCooldown("Cascade");
            }
        }

        private static bool CanDivineStar
        {
            get
            {
                return Settings.UseDivineStar && TalentManager.HasTalent(17) && !Spell.SpellOnCooldown("Divine Star");
            }
        }

        #endregion

        #region Targets

        private static WoWUnit BestShadowWordDeathUnit()
        {
            WoWUnit bestHostileEnemy;

            if (Me.InBgOrArena())
            {
                // Ascending for a BG / Arena (get lowest HP first).
                return Unit.CachedNearbyAttackableUnitsAttackingUs(Me.Location, 40)
                                       .Where(t => t.IsValid && t.HealthPercent < 20)
                                       .OrderBy(t => t.CurrentHealth)
                                       .FirstOrDefault(t => Me.IsFacing(t));
            }

            bestHostileEnemy = Unit.CachedNearbyAttackableUnitsAttackingUs(Me.Location, 40)
                                       .Where(t => t.IsValid && t.HealthPercent < 20)
                                       .OrderByDescending(t => t.CurrentHealth)
                                       .FirstOrDefault(t => Me.IsFacing(t));

            return bestHostileEnemy;
        }


        #endregion Targets

    }
}
